package com.gomcarter.frameworks.base.config;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.gomcarter.frameworks.base.annotation.JsonWrapper;
import com.gomcarter.frameworks.base.common.JsonMapper;
import com.gomcarter.frameworks.base.common.RequestUtils;
import com.gomcarter.frameworks.base.converter.DateEditor;
import com.gomcarter.frameworks.base.converter.LocalDateEditor;
import com.gomcarter.frameworks.base.converter.LocalDateTimeEditor;
import com.gomcarter.frameworks.base.converter.LocalTimeEditor;
import com.gomcarter.frameworks.base.exception.BizLogicException;
import com.gomcarter.frameworks.base.exception.CustomException;
import com.gomcarter.frameworks.base.json.*;
import com.google.common.base.Strings;
import com.talkyun.utils.revised.http.HttpResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.annotation.Resource;
import javax.management.ReflectionException;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Date;
import java.util.stream.Collectors;

/**
 * 响应配置
 *
 * @author z.y.h
 * create on 2023/3/3 13:14
 */
@Slf4j
@ControllerAdvice
public class BaseRespAdvice implements ResponseBodyAdvice<Object> {
    @Resource
    private BaseConfProperties properties;
    private static final String ERROR_MESSAGE_PLACEHOLDER = "{}操作失败, url:{}, method:{}, ip: {}, Referer: {}, UA: {}, params: {}, cookie: {},header: {},";

    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> converterType) {
        return !methodParameter.hasMethodAnnotation(JsonIgnore.class);
    }

    @Override
    public Object beforeBodyWrite(Object obj, MethodParameter methodParameter, MediaType mediaType,
                                  Class<? extends HttpMessageConverter<?>> converterType,
                                  ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        // 跳过视图
        if (obj instanceof ModelAndView) {
            return obj;
        }

        // 构造统一包装返回对象
        JsonObject jsonObject = buildJsonObject(obj, serverHttpResponse);

        // 统一异常处理返回的数据对象，就使用 JsonError 带的JsonWrapper，其他使用标准的方法注解
        JsonWrapper methodAnnotation = jsonObject instanceof JsonError
                ? ((JsonError) jsonObject).getErrorMethodAnnotation()
                : methodParameter.getMethodAnnotation(JsonWrapper.class);

        // 按照JsonWrapper做包装对象调整
        return doTransformWrapObject(methodAnnotation, jsonObject);
    }

    private JsonObject buildJsonObject(Object obj, ServerHttpResponse serverHttpResponse) {
        if (obj == null) {
            return new JsonSuccess();
        }
        if (obj instanceof JsonObject) {
            return (JsonObject) obj;
        }
        if (serverHttpResponse instanceof ServletServerHttpResponse
                && !HttpResult.SUCCESS_STATUS.contains(((ServletServerHttpResponse) serverHttpResponse).getServletResponse().getStatus())) {
            return new JsonError(obj.toString());
        }
        return new JsonData(obj);
    }

    private Object doTransformWrapObject(JsonWrapper methodAnnotation, JsonObject jsonObject) {
        // 如果没有指定注解 JsonWrapper 就直接返回外部包装对象
        if (methodAnnotation == null) {
            return buildNewJsonObject(jsonObject);
        }

        // 如果指定 JsonObject 就直接返回外部包装对象
        Class<?> wrapClass = methodAnnotation.value();
        if (wrapClass == JsonObject.class) {
            return buildNewJsonObject(jsonObject);
        }

        // 如果指定 NoWrapObject 并且不是统一异常处理过来的 就直接返回内部业务对象
        if (wrapClass == NoWrapObject.class) {
            return jsonObject instanceof JsonError ? buildNewJsonObject(jsonObject) : jsonObject.getData();
        }

        // 如果指定 SimpleJsonObject 就重新构建精简返回对象
        if (wrapClass == SimpleJsonObject.class) {
            return new SimpleJsonObject(jsonObject);
        }

        // 按照业务指定对象返回
        Class<?> argClass = JsonObject.class;
        try {
            Constructor<?> constructor = wrapClass.getConstructor(argClass);
            return constructor.newInstance(jsonObject);
        } catch (NoSuchMethodException e) {
            String msg = String.format("包装对象类未提供参数对象类的构造方法： wrapClass=%s, argClass=%s, raw=%s",
                    wrapClass.getName(), argClass.getName(), jsonObject.getMessage());
            log.warn(msg, e);
            return buildErrorJsonObject(ErrorCode.nullPointer.getCode(), msg);
        } catch (InvocationTargetException | IllegalAccessException | InstantiationException e2) {
            String msg2 = String.format("包装对象类根据参数对象类构造失败： wrapClass=%s, argClass=%s, raw=%s",
                    wrapClass.getName(), argClass.getName(), jsonObject.getMessage());
            log.warn(msg2, e2);
            return buildErrorJsonObject(ErrorCode.nullPointer.getCode(), msg2);
        }
    }

    /**
     * 构建新的 JsonObject，剔除 JsonError 里面的 errorMethodAnnotation
     */
    private JsonObject buildNewJsonObject(JsonObject jsonObject) {
        if (!(jsonObject instanceof JsonError)) {
            return jsonObject;
        }
        JsonObject newJsonObject = new JsonObject();
        newJsonObject.setCode(jsonObject.getCode());
        newJsonObject.setMessage(jsonObject.getMessage());
        newJsonObject.setData(jsonObject.getData());
        return newJsonObject;
    }

    /**
     * 构建错误 JsonObject，提示未能够按照业务指定对象返回包装对象
     */
    private JsonObject buildErrorJsonObject(Integer code, String message) {
        JsonObject errorJsonObject = new JsonObject();
        errorJsonObject.setCode(code);
        errorJsonObject.setMessage(message);
        return errorJsonObject;
    }

    @ResponseBody
    @ExceptionHandler(Throwable.class)
    public JsonError exceptionHandler(HttpServletRequest request, Exception exception, HandlerMethod handlerMethod) {
        printErrorMessage(request, exception);
        JsonWrapper errorMethodAnnotation = handlerMethod != null ? handlerMethod.getMethodAnnotation(JsonWrapper.class) : null;
        if (exception instanceof CustomException) {
            return new JsonError(exception.getMessage(), ((CustomException) exception).getCode(), errorMethodAnnotation);
        }
        // 参数异常
        if (exception instanceof MissingServletRequestParameterException) {
            return new JsonError(exception.getMessage(), ErrorCode.paramError.getCode(), errorMethodAnnotation);
        }
        // 接口参数不匹配异常
        if (exception instanceof MethodArgumentNotValidException) {
            return new JsonError(getErrorInfo(((MethodArgumentNotValidException) exception).getBindingResult()),
                    ErrorCode.paramError.getCode(), errorMethodAnnotation);
        }
        // 参数绑定异常
        if (exception instanceof BindException) {
            return new JsonError(getErrorInfo(((BindException) exception).getBindingResult()),
                    ErrorCode.paramError.getCode(), errorMethodAnnotation);
        }
        // 参数约束条件异常
        if (exception instanceof ConstraintViolationException) {
            return new JsonError(((ConstraintViolationException) exception)
                    .getConstraintViolations()
                    .stream()
                    .map(ConstraintViolation::getMessage)
                    .collect(Collectors.joining(";")), ErrorCode.paramError.getCode(), errorMethodAnnotation);
        }
        // 非法参数异常
        if (exception instanceof IllegalArgumentException) {
            return new JsonError(exception.getMessage(), ErrorCode.illArgError.getCode(), errorMethodAnnotation);
        }

        //空指针异常
        boolean useRealErrorMsg = errorMethodAnnotation == null ? handlerMethod != null && handlerMethod.hasMethodAnnotation(JsonIgnore.class)
                : errorMethodAnnotation.responseCause();
        if (exception instanceof NullPointerException) {
            return buildJsonError(useRealErrorMsg, exception, ErrorCode.nullPointer, errorMethodAnnotation);
        }
        // SQL异常 && 反射异常
        if (exception instanceof SQLException || exception instanceof ReflectionException) {
            return buildJsonError(useRealErrorMsg, exception, ErrorCode.sqlError, errorMethodAnnotation);
        }
        // 其他异常
        return buildJsonError(useRealErrorMsg, exception, ErrorCode.defaultError, errorMethodAnnotation);
    }

    private JsonError buildJsonError(boolean useRealErrorMsg, Throwable throwable, ErrorCode defaultErrorCode, JsonWrapper errorMethodAnnotation) {
        String errorMsg = useRealErrorMsg ? throwable.getMessage() : defaultErrorCode.getMsg();
        if (useRealErrorMsg && throwable instanceof NullPointerException) {
            // 空指针异常如果使用exception.getMessage得到的值是null, 会对调用方产生误导, 因此使用exception.toString() 得到java.lang.NullPointerException加以区分
            errorMsg = throwable.toString();
        }
        return new JsonError(errorMsg, defaultErrorCode.getCode(), errorMethodAnnotation);
    }

    private void printErrorMessage(HttpServletRequest request, Exception exception) {
        if (needLogDemotion(exception)) {
            log.warn(ERROR_MESSAGE_PLACEHOLDER,
                    this.getClass().getName(),
                    request.getRequestURI(),
                    request.getMethod(),
                    RequestUtils.getIp(request),
                    request.getHeader("Referer"),
                    request.getHeader("User-Agent"),
                    JsonMapper.buildNonNullMapper().toJson(request.getParameterMap()),
                    JsonMapper.buildNonNullMapper().toJson(request.getCookies()),
                    JsonMapper.buildNonNullMapper().toJson(RequestUtils.headerMap(request)),
                    exception);
        } else {
            log.error(ERROR_MESSAGE_PLACEHOLDER,
                    this.getClass().getName(),
                    request.getRequestURI(),
                    request.getMethod(),
                    RequestUtils.getIp(request),
                    request.getHeader("Referer"),
                    request.getHeader("User-Agent"),
                    JsonMapper.buildNonNullMapper().toJson(request.getParameterMap()),
                    JsonMapper.buildNonNullMapper().toJson(request.getCookies()),
                    JsonMapper.buildNonNullMapper().toJson(RequestUtils.headerMap(request)),
                    exception);
        }
    }

    private boolean needLogDemotion(Exception exception) {
        //业务逻辑异常默认降级处理
        if (exception instanceof BizLogicException) {
            return true;
        }
        //开启日志降级处理
        if (!properties.isEnableLogPrintDegrade() || ObjectUtils.isEmpty(properties.getLogPrintDegradePrefixList())) {
            return false;
        }
        String message = Strings.isNullOrEmpty(exception.getMessage()) ? exception.toString() : exception.getMessage();
        return properties.getLogPrintDegradePrefixList().stream().anyMatch(message::contains);
    }

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.setAutoGrowCollectionLimit(properties.getAutoGrowCollectionLimit());
        binder.registerCustomEditor(Date.class, new DateEditor());
        binder.registerCustomEditor(LocalTime.class, new LocalTimeEditor());
        binder.registerCustomEditor(LocalDate.class, new LocalDateEditor());
        binder.registerCustomEditor(LocalDateTime.class, new LocalDateTimeEditor());
    }

    private String getErrorInfo(BindingResult bindingResult) {
        StringBuilder stringBuilder = new StringBuilder();
        bindingResult.getFieldErrors().forEach((error) -> {
            stringBuilder.append(error.getDefaultMessage()).append(";");
        });
        return stringBuilder.toString();
    }
}